#!/bin/bash

### BEGIN INIT INFO
# Provides:				6rd
# Required-Start:		$network
# Required-Stop:		$network
# Default-Start:		2 3 4 5
# Default-Stop:			0 1 6
# Short-Description:	6RD Configuration Script
# Description:			Bring up/down 6RD interface
### END INIT INFO

# /etc/init.d/6rd
###############################################################################
# 6rd : Script to start / stop 6rd IPv6
#
# Changelog:
# Version 1.0rc8 (20110323)
#   - Improvements in IPv4 detection from Bruce Pinsky (bep)
#   - Allows now several LAN interfaces.
#	- Allows specific routes for IPv6 WAN
#	- Improved /32 6rd prefix handling (not tested in the wild)
#	- Improved the stop behaviour
###############################################################################

# Some things that run always
ipfile="/tmp/ipv4.txt"

if [ -r /etc/default/6rd ]; then
    . /etc/default/6rd
else
	echo "6rd : Error : Can't get the values from the /etc/default/6rd file"
	exit 1
fi

function is_public_ipv4 {
	# $1 : IPv4 to test
	# Will return "yes" or "no"

	nonpublic_ipv4_regexp=( "^10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" \
						    "^192\.168\.[0-9]{1,3}\.[0-9]{1,3}" \
						    "^172\.1[6-9]\.[0-9]{1,3}\.[0-9]{1,3}" \
						    "^172\.2[0-9].\.[0-9]{1,3}\.[0-9]{1,3}" \
						    "^22[4-9]\.[0-9]{1,3}.\.[0-9]{1,3}\.[0-9]{1,3}" \
						    "^2[3-5][0-9]\.[0-9]{1,3}.\.[0-9]{1,3}\.[0-9]{1,3}" \
						  )

	if [ $(is_valid_ipv4 $1) = "no" ]; then
		printf "no"
	else
		for ((i=0; i<${#nonpublic_ipv4_regexp[*]}; i++)); do
			if [[ $1 =~ ${nonpublic_ipv4_regexp[${i}]} ]]; then
				printf "no"
			else
				printf "yes"
			fi
		done
	fi
}

function is_valid_ipv4 {
	# $1 : IPv4 to test
	# Will return "yes" or "no"

	valid_ipv4_regexp="[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" 

	if [[ $1 =~ $valid_ipv4_regexp ]]; then
		printf "yes"
	else
		printf "no"
	fi
}		

function ipv4to6rd {
	# $1 : IPv4 to consider (External IPv4 of the site)
	# $2 : IPv6 Prefix (6rd Prefix from ISP)
	# Will return the customer 6rd prefix
	
	# Change the IPv4 into a 8-hex-digit string
	ipv4_hex=$(ipv4tohex $1)
	
	# Get IPv6 Mask and prefix
	ipv6_6rd_mask=$(ipv6pfxtomask $2)
	ipv6_6rd_net=$(ipv6pfxtonet $2)
	
	# Add the IPv4 to the Net
	if [ $(( $ipv6_6rd_mask % 4 )) = 0 ]
	then
		# Case where the mask falls on an hex digit
		printf -v ipv6_6rd_cust  $(( (0x$ipv6_6rd_net << (64 - $ipv6_6rd_mask)) + ( 0x$ipv4_hex << (32 - $ipv6_6rd_mask)) ))
	else
		# Case where not ...
		printf -v ipv6_6rd_cust  $(( (0x$ipv6_6rd_net << (64 - ((1 + $ipv6_6rd_mask / 4) * 4))) + ( 0x$ipv4_hex << (32 - $ipv6_6rd_mask)) ))
	fi

	
	# Customer subnet is now 6rd_mask + 32 (ip address size)
	
	printf "%16x" $ipv6_6rd_cust
}

function ipv6pfxtomask {
	# $1 : prefix IPv6 (with mask)
	# Will return the subnet mask (without "/", ":")
	
	printf $(echo $1 | cut -f 2 -d "/")
}

function ipv6pfxtonet {
	# $1 : prefix IPv6 (with mask)
	# Will return the complete subnet address (without "/", but with ":")
	# and will include also prepending 0

	left=$(echo $1 | cut -f 1 -d "/")
	mask=$(ipv6pfxtomask $1)

	# Get the prefix with ":"
	# This syntax is possible because the first block is alway 4 hex digits (2000::/3)
	# If one day this changes, well ... this script will need to be changed !
	# Furthermore, all 6rd deployments use prefixes between 16 and 32, so max two blocks.

	# First get the 4 first digits
	printf -v isp_pfx1 "%04x" 0x$(echo $left | cut -f 1 -d ":")

	# The second block.
	# There is at least an empty string here, so it should display 0000
	block2=$(echo $left | cut -f 2 -d ":")
	if [ "$block2" = "" ]
	then
		isp_pfx2="0000"
	else
		printf -v isp_pfx2 "%04x" 0x$block2
	fi
	printf -v isp_pfx "$isp_pfx1$isp_pfx2"
	printf -v hexmask "%x" $(( (2**$mask-1) << (64-$mask) ))
	printf -v isp_pfx_64 "%16x" $(( (0x$isp_pfx << 32) & 0x$hexmask ))

	# Output the final one without ":"
	printf "%x" $(( 0x$isp_pfx_64 >> ((64-$mask)/4*4)))
}


function ipv4tohex {
	# $1 : value of the IPv4 (with dots)
	# Will return the IPv4 in hex
	
	printf "%02x%02x%02x%02x" $(echo $ipv4 | tr "." " ")
}

function hextoip {
	# $1 : value of the 1st 64 bits
	# $2 : value of the last 64 bits
	# Will return aggregated first part of v6 and second part of v6
	# following the rules of aggregation

	ipv6[1]=0x${1:0:4}
	ipv6[2]=0x${1:4:4}
	ipv6[3]=0x${1:8:4}
	ipv6[4]=0x${1:12}
	ipv6[5]=0x${2:0:4}
	ipv6[6]=0x${2:4:4}
	ipv6[7]=0x${2:8:4}
	ipv6[8]=0x${2:12}

	# Check the length of longest sequence of 0-blocks. Remember position and length
	longest=0
	longestcur=0
	longestidx=0
	longestcuridx=0

	for idx in 1 2 3 4 5 6 7 8
	do
		if [ ${ipv6[$idx]} = "0x0000" ]
		then
			if [ $longestcur = 0 ]
			then
				longestcuridx=$idx
			fi
			longestcur=$(($longestcur+1))
		else
			if [ $longestcur \> $longest ]
			then
				longest=$longestcur
				longestidx=$longestcuridx
			fi
			longestcuridx=0
			longestcur=0
		fi
	done

	# In case we reached the 8th with 0x0000 ...
	#longest=$longestcur
	#longestidx=$longestcuridx


	# Now compress for real where it has to be
	idx=1
	ipv6_new=""
	while [ $idx -le 8 ]
	do
		if [[ $idx = 1 && $longestidx != 1 ]]
		then
			printf -v ipv6_new "%x" ${ipv6[$idx]}
		else
			if [ $idx = $longestidx ]
			then
				printf -v ipv6_new "%s:" $ipv6_new
				idx=$(($idx+$longest))
				continue
			else
				printf -v ipv6_new "%s:%x" $ipv6_new ${ipv6[$idx]}
			fi
		fi
		idx=$(($idx+1))
	done

	printf $ipv6_new
}


function ipv6subnet {
	# $1 : IPv6 6rd prefix customer
	# $2 : increment for the last nibble in the first /64:
	# 2001:db8:0001:000X
	#                  ^ That one

	printf "%16x" $(( 0x$1 + $2 ))
}

function get_ipv4_from_iproute2 {
	# $1 : Interface name
	# Returns IPv4 IP on the interface, or an empty string.
	
	ipv4=$(ip -4 addr show dev $glob_wan_iface scope global | cut -s -d/ -f1 | cut -d' ' -f6)
	
	printf "$ipv4"
}

function do_cmd {
	# $1 : Command to do
	# Will do a command after checking if it's allowed to do it.

	if [ "$do_nothing" = "yes" ]; then
		do_debug "Command : $1"
	else
		$1
	fi
}

function do_debug {
	# $1 : Displays info on the screen if required in the config
	
	if [ "$do_nothing" = "yes" ]; then
		echo "6rd : $1"
	fi
}

# implements startup of 6rd
start() {

	if [ "$do_nothing" = "yes" ]; then
		do_debug "WARNING : do_nothing=\"yes\" configured - Not configuring your box, and enabling debug."
	fi

	#### STEP 1 : Determine Public IPv4 Address

	# Check if there is a static default configured in /etc/default/6rd
	# if not, then try to get it from the WAN interface
	# If it's not public, check if $glob_wan_is_private is defined (ISP does LSN/CGN and 6rd GW is in the private domain)
	# last, if still no public IP, try to get it via a web service (you're natted)

	# We'll need the WAN IP anyhow, so start with it.
	ipv4_wan=$( get_ipv4_from_iproute2 $glob_wan_iface )
	# Check if we got an IP that looks like an IPv4, otherwise return an error
	if [ $( is_valid_ipv4 $ipv4_wan) != "yes" ]; then
		echo "6rd : Error : Can't get IPv4 WAN address ($glob_wan_iface)."
		exit 1
	fi
	
	do_debug "Info : EthWAN ($glob_wan_iface): $ipv4_wan"

	if [ $( is_valid_ipv4 $glob_wan_ip ) = "yes" ]; then
		do_debug "Info : WAN IPv4 defined in config : $glob_wan_ip"
		ipv4=$glob_wan_ip
	else
		do_debug "Info : WAN IPv4 not defined in config."
		if [ $( is_public_ipv4 $ipv4_wan ) = "yes" ]; then
			do_debug "Info : EthWAN IPv4 is public."
			ipv4=$ipv4_wan
		else
			do_debug "Info : EthWAN IPv4 is not public."
			if [ "$glob_wan_is_private" = "yes" ]; then
				do_debug "Info : EthWAN IPv4 is not public, and it's normal."
				ipv4=$ipv4_wan
			else
				do_debug "Info : EthWAN IPv4 is not public, and it's not normal."
				do_debug "Info : WAN IPv4 has to be defined with an external service."			
				
				# Need to get my ip automatically.
				# Right now this page answers only on v4.
				if [ "$glob_download_tool" = "curl" ] ; then
					curl --silent --show-error --interface $ipv4_wan --output $ipfile http://www.whatismyip.fr/raw/
				else
					wget --no-verbose --bind-address=$ipv4_wan --output-document=$ipfile http://www.whatismyip.fr/raw/
				fi
		
				ipv4=$(cat $ipfile)
			fi
		fi
	fi

	# Check again if we got an IP that looks like an IPv4, otherwise return an error
	if [ $( is_valid_ipv4 $ipv4) != "yes" ]; then
		echo "6rd : Error : Can't get IPv4 public address."
		exit 1
	fi

	# Maths and String part ... funny stuff !
	# Check that the prefix is supported		
	ipv6_6rd_mask=$(ipv6pfxtomask $glob_6rd_net)
	if [ $ipv6_6rd_mask != 24 -a $ipv6_6rd_mask != 28 -a $ipv6_6rd_mask != 32 ]; then
		echo "6rd : Warning : Very strange to see a subnetmask other than /24, /28, or /32."			
		# In fact the script supports it, but it's most likely a config error.
		#exit
	fi
	
	# From the Public IPv4 address, we deduct the IPv6 customer prefix.
	ipv6_6rd_prefix=$(ipv4to6rd $ipv4 $glob_6rd_net)
	
	# The customer prefix is either a /24 or /28 : 1st subnet for tunnel, 2nd+ for LAN
	ipv6_wan=$(ipv6subnet $ipv6_6rd_prefix 0x0)
	ipv6=$(hextoip $ipv6_wan 0000000000000001)

	# display status
	do_cmd "echo 6rd - PUBLIC IPv4 address is $ipv4"
	do_cmd "echo 6rd - PUBLIC IPv6 address is $ipv6"

	# echo Configuring...
	# Configure the tunnel
	do_cmd "ip tunnel add $glob_6rd_iface mode sit ttl 64 local $ipv4_wan"
	do_cmd "ip tunnel 6rd dev $glob_6rd_iface 6rd-prefix $glob_6rd_net"
	do_cmd "ip link set $glob_6rd_iface up"
	if [ $ipv6_6rd_mask != 32 ]; then
		do_cmd "ip -6 addr add $ipv6/64 dev $glob_6rd_iface"
	fi

	# Configure the LAN
	i=1
	for lan_iface in $glob_lan_ifaces ; do
		do_debug "Info : Calculating LAN IPv6 for $lan_iface"
		# The customer prefix is = /32 : Subnet only the LAN (doesn't work ?)
		if [ $ipv6_6rd_mask = 32 ]; then
			ipv6_lan=$ipv6
		else
			# This will work in fact only with /28 and /24 ... no idea about /32
			# We in fact just pick up the next /64 in our allocated /60 or /56
			ipv6_lan=$(ipv6subnet $ipv6_6rd_prefix $i)
			ipv6_lan=$(hextoip $ipv6_lan 0000000000000001)
		fi
		do_cmd "echo 6rd - $lan_iface IPv6 address is $ipv6_lan"
		do_cmd "ip -6 addr add $ipv6_lan/64 dev $lan_iface"
		if [ $ipv6_6rd_mask = 32 ]; then
			do_cmd "echo 6rd - WARNING : 6rd prefix /32 : calculated /64 configured only on first LAN."
			break
		fi
		((i++))
	done

	# Configure the Routes
	if [ "$glob_wan_default_route" = "yes" ]; then
		do_cmd "ip -6 route add 2000::/3 via ::$glob_6rd_br dev $glob_6rd_iface"
	else
		do_debug "Info : Not setting default route pointing to the WAN."
	fi

	for ipv6_route in $glob_wan_routes ; do
		do_cmd "ip -6 route add $ipv6_route via ::$glob_6rd_br dev $glob_6rd_iface"
	done

	# Restart RADVD if necessary
	if [ -x /etc/init.d/radvd ]; then
		do_cmd "/etc/init.d/radvd restart"
	else
		do_debug "Not able to restart /etc/init.d/radvd (not found or not executable)."
	fi

	# Restart DHCPv6 server (wide) if necessary
	if [ -x /etc/init.d/wide-dhcpv6-server ]; then
		do_cmd "/etc/init.d/wide-dhcpv6-server restart"
	else
		do_debug "Not able to restart /etc/init.d/wide-dhcpv6-server (not found or not executable)."
	fi

	# log the realized operation
	do_cmd "logger \"IPv6 using 6rd configured (ipv4 public: $ipv4, ipv6: $ipv6)\""
}

# stops 6rd and flushes all routes and addresses
stop() {

	if [ "$do_nothing" = "yes" ]; then
		do_debug "WARNING : do_nothing=\"yes\" configured - Not configuring your box, and enabling debug."
	fi

	# Clean the WAN
	do_cmd "ip -6 route flush dev $glob_6rd_iface"
	do_cmd "ip -6 addr flush dev $glob_6rd_iface scope global"
	do_cmd "ip link set dev $glob_6rd_iface down"
	do_cmd "ip tunnel del $glob_6rd_iface"
	
	# Probably you don't want to do this !
	# do_cmd "ip -6 route flush scope global"
	
	# Clean the LAN interfaces configured
	for lan_iface in $glob_lan_ifaces ; do
		do_cmd "ip -6 route flush dev $lan_iface"
		do_cmd "ip -6 addr flush dev $lan_iface scope global"		
	done
	
	# Restart RADVD if necessary
	if [ -x /etc/init.d/radvd ]; then
		do_cmd "/etc/init.d/radvd restart"
	else
		do_debug "Not able to restart /etc/init.d/radvd (not found or not executable)."
	fi
	
	# Restart DHCPv6 server (wide) if necessary
	if [ -x /etc/init.d/wide-dhcpv6-server ]; then
		do_cmd "/etc/init.d/wide-dhcpv6-server restart"
	else
		do_debug "Not able to restart /etc/init.d/wide-dhcpv6-server (not found or not executable)."
	fi
}

status() {
	echo "IPV6 STATUS"
	echo "--"
	echo "Addresses"
	echo "---------"
	ip -6 addr show
	echo " "
	echo "Routes"
	echo "------"
	ip -6 route show
	echo " "
	echo "Neighbors"
	echo "------"
	ip -6 neigh show
}


# tests startup of 6rd
test() {
	do_nothing="yes"
	start
}

restart() {
	stop
	start
}


# Carry out specific functions when asked to by the system
case "$1" in
  start)
	echo "Starting script 6rd"
		start
	;;
  stop)
	echo "Stopping script 6rd"
		stop
   ;;
  test) 
	echo "Testing script 6rd"
	echo
		test
	;;
  status)
		status
	;;
  restart)
		restart
		;;
  *)
	echo "Usage: /etc/init.d/6rd {start|stop|restart|test|status}"
	exit 1
	;;
esac

exit 0

# This is a 3-Clause BSD Licence, GPL-Compatible
# http://www.gnu.org/licenses/license-list.html#ModifiedBSD
# Please be aware that the copyright information MUST be maintained.
#
# (C) Carlos M. Martinez-Cagnazzo, 2008. Original 6to4 Script.
#     http://cagnazzo.name/drupal/6to4
#
# (C) Guillaume Leclanche, 2010-2011. Completed for 6rd and non-NATed routers.
#     guillaume at leclanche.net
#
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
#
# - Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.
# - The names of the authors may not be used to endorse or promote products 
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR  
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
